#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#include <DNSServer.h>//密码直连将其三个库注释
#include <ESP8266WebServer.h>
#include <CustomWiFiManager.h>

#include <time.h>                       
#include <sys/time.h>                  
#include <coredecls.h>      


//#include "SH1106Wire.h"   //1.3寸用这个
#include "SSD1306Wire.h"    //0.96寸用这个
#include "OLEDDisplayUi.h"
#include "HeFeng.h"
#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"

/***************************
   Begin Settings
 **************************/

const char* WIFI_SSID = "xiaokai";  //填写你的WIFI名称及密码
const char* WIFI_PWD = "12345678k";

const char* BILIBILIID = "167480182";  //填写你的B站账号

//由于太多人使用我的秘钥，导致获取次数超额，所以不提供秘钥了，大家可以到https://dev.heweather.com/获取免费的
//const char* HEFENG_KEY = "xxxxxxxxxxxxxxxxxxxxxx";//填写你的和风天气秘钥
const char* HEFENG_KEY = "300db3a6b17447f7bbf92aca06a6aee3";
//const char* HEFENG_LOCATION = "CN101290703";
const char* HEFENG_LOCATION = "CN101282005";
//填写你的城市ID,可到https://where.heweather.com/index.html查询
//const char* HEFENG_LOCATION = "auto_ip";//自动IP定位

#define TZ              8      // 中国时区为8
#define DST_MN          0      // 默认为0

const int UPDATE_INTERVAL_SECS = 1 * 60; // 5分钟更新一次天气
const int UPDATE_CURR_INTERVAL_SECS = 2 * 59; // 2分钟更新一次粉丝数

const int I2C_DISPLAY_ADDRESS = 0x3c;  //I2c地址默认
#if defined(ESP8266)
const int SDA_PIN = 0;  //引脚连接
const int SDC_PIN = 2;  //
#endif

const String WDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};  //星期
const String MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};  //月份

// web配网页面自定义我的图标请随便使用一个图片转base64工具转换https://tool.css-js.com/base64.html, 64*64
// 使用 PROGMEM 关键字,告诉编译器 “ 把这个信息存到程序存储空间 ”，而不是存到“ 动态内存 ”
// 程序存储空间FLASH是不可改变的，因此定义时加关键字const 是个好的习惯。
const char Icon[] PROGMEM = "1";

/***************************
   End Settings
 **************************/
 
//SH1106Wire     display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);   // 1.3寸用这个
SSD1306Wire     display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);   // 0.96寸用这个
//上面的display是OLED实例化对象名,采用的是参数构造
OLEDDisplayUi   ui( &display );
//display是OLED实例化的对象,则是指针可以作为父类指针
//参数构造了UI的实例化对象,其中这个对象的一个变量初始化成了OLED的指针

HeFengCurrentData currentWeather; //实例化对象
HeFengForeData foreWeather[3];
HeFeng HeFengClient;
//粉丝更新来自上面这个实例化对象

#define TZ_MN           ((TZ)*60)   //时间换算
//相差分钟为timezone*60
#define TZ_SEC          ((TZ)*3600)
//相差分钟为timezone*3600
#define DST_SEC         ((DST_MN)*60)
//秒钟是分钟的60倍

time_t now; //实例化时间
const time_t memDay = 1642377600;
struct tm* memDayInfo = localtime(&memDay);
const uint8_t memDayYr = memDayInfo->tm_year;
const int16_t memDayDt = memDayInfo->tm_yday;

bool readyForWeatherUpdate = false; // 天气更新标志
bool first = true;  //首次更新标志
long timeSinceLastWUpdate = 0;    //上次更新后的时间
long timeSinceLastCurrUpdate = 0;   //上次天气更新后的时间

String fans = "-1"; //粉丝数
String delta_dys = "-1";

void drawProgress(OLEDDisplay *display, int percentage, String label);   //提前声明函数
void updateData(OLEDDisplay *display);
void updateDatas(OLEDDisplay *display);
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();
void drawAnniversary(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void returnDelta(void);
void returnDelta(int& _deltaDays,int& _deltaYears);

//添加框架
//此数组保留指向所有帧的函数指针
//框架是从右向左滑动的单个视图
// FrameCallback frames[] = { drawDateTime, drawAnniversary,drawCurrentWeather, drawForecast};
FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast};

//页面数量
int numberOfFrames = sizeof(frames)/sizeof(FrameCallback);

OverlayCallback overlays[] = { drawHeaderOverlay }; //覆盖回调函数
int numberOfOverlays = sizeof(overlays)/sizeof(OverlayCallback);  //覆盖数

void setup() {
  Serial.begin(9600);
  Serial.println();
  Serial.println();

  // 屏幕初始化
  display.init();
  display.clear();
  display.display();

  //display.flipScreenVertically(); //屏幕翻转
  display.setContrast(255); //屏幕亮度

  //Web配网，密码直连请注释
  webconnect();
  
  // 用固定密码连接，Web配网请注释
  //wificonnect();

  ui.setTargetFPS(30);  //刷新频率

  // 指示栏活动和非活动符号外形
  ui.setActiveSymbol(activeSymbole); //设置活动符号
  ui.setInactiveSymbol(inactiveSymbole); //设置非活动符号

  // 符号位置
  // 你可以把这个改成TOP, LEFT, BOTTOM, RIGHT
  // 指示栏位置设置： TOP, LEFT, BOTTOM, RIGHT
  ui.setIndicatorPosition(BOTTOM);

  // 定义第一帧在栏中的位置
  // 设置指示栏指示条移动的方向
  ui.setIndicatorDirection(LEFT_RIGHT);

  // 屏幕切换方向
  // 您可以更改使用的屏幕切换方向 SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
  // 设置从上一帧过渡到下一帧的动画效果
  ui.setFrameAnimation(SLIDE_LEFT);

  ui.setFrames(frames, numberOfFrames); // 设置框架
  // 设置每一帧图片持续时间, 单位为ms 
  ui.setTimePerFrame(9000); //设置切换时间
  
  ui.setOverlays(overlays, numberOfOverlays); //设置覆盖

  // UI负责初始化显示
  ui.init();
  

  configTime(TZ_SEC, DST_SEC, "ntp.ntsc.ac.cn", "ntp1.aliyun.com"); //ntp获取时间，你也可用其他"pool.ntp.org","0.cn.pool.ntp.org","1.cn.pool.ntp.org","ntp1.aliyun.com"
  delay(200);

   display.flipScreenVertically(); //屏幕反转

}

void loop() {
  if (first) {  //首次加载
    // 要画进度条,所以传入OLED对象
    updateDatas(&display);
    delay(50);
    returnDelta();
    HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
    HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
    first = false;
  }
  if (millis() - timeSinceLastWUpdate > (1000L * UPDATE_INTERVAL_SECS)) { //屏幕刷新
    setReadyForWeatherUpdate();
    timeSinceLastWUpdate = millis();
  }
  if (millis() - timeSinceLastCurrUpdate > (1000L * UPDATE_CURR_INTERVAL_SECS)) { //粉丝数更新
    HeFengClient.fans(&currentWeather, BILIBILIID);
    fans = String(currentWeather.follower);
    timeSinceLastCurrUpdate = millis();
  }
  
  // 判断条件是 标志位准备就绪 && UI属于稳定状态
  if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) { //天气更新
    updateData(&display);
  }

  // 刷新OLED屏幕，返回值为在当前设置的刷新率下显示完当前图像后所剩余的时间
  int remainingTimeBudget = ui.update(); //剩余时间预算

  // 我们可以在这剩余的时间做一些事情，但是如果时间不够，就不要做任何事情了
  if (remainingTimeBudget > 0) {
    //你可以在这里工作如果你低于你的时间预算。
    delay(remainingTimeBudget);
  }
  
}

//void wificonnect() {  //WIFI密码连接，Web配网请注释
//  WiFi.begin(WIFI_SSID, WIFI_PWD);
//  while (WiFi.status() != WL_CONNECTED) {
//    Serial.print('.');
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_5);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_6);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_7);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_8);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_1);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_2);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_3);
//    display.display();
//    delay(80);
//    display.clear();
//    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_4);
//    display.display();
//  }
//  Serial.println("");
//  delay(500);
//}

void webconnect() {  ////Web配网，密码直连将其注释
  display.clear();
  display.drawXbm(0, 0, 128, 64, _xbm); //显示哔哩哔哩
  display.display();

  WiFiManager wifiManager;  //实例化WiFiManager
  wifiManager.setDebugOutput(false); //关闭Debug
  //wifiManager.setConnectTimeout(10); //设置超时
  // FPSTR表示从FLASH中读取Icon??
  wifiManager.setHeadImgBase64(FPSTR(Icon)); //设置图标(此处使用图片转base64工具转换https://tool.css-js.com/base64.html, 64*64
  wifiManager.setPageTitle("欢迎来到小屏幕的WiFi配置页");  //设置页标题

  if (!wifiManager.autoConnect("MiniScreen-IOT-Display")) {  //AP模式
    Serial.println("连接失败并超时");
    //重新设置并再试一次，或者让它进入深度睡眠状态
    ESP.restart();
    delay(1000);
  }
  Serial.println("connected...^_^");
  yield();
}

void drawProgress(OLEDDisplay *display, int percentage, String label) {    //绘制进度
  display->clear();
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  //居中显示
  display->setFont(ArialMT_Plain_10);
  //设置字体
  display->drawString(64, 10, label);
  //显示标签
  display->drawProgressBar(2, 28, 124, 10, percentage);
  //描绘进度条
  display->display();
  //进行显示
}

void updateData(OLEDDisplay *display) {  //天气更新
  returnDelta();
  HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
  HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
  readyForWeatherUpdate = false;
  //更新完数据将更新标志位置零
}

void updateDatas(OLEDDisplay *display) {  //首次天气更新
  drawProgress(display, 0, "Updating delta days...");
  returnDelta();
  // 获取在一起总天数
  drawProgress(display, 33, "Updating weather...");
  HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
  // 获取天气信息
  drawProgress(display, 66, "Updating forecasts...");
  HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
  // 获取预报信息
  readyForWeatherUpdate = false;
  // 更新完数据将更新标志位置零
  drawProgress(display, 100, "Done...");
  delay(200);
  
}

void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //显示时间
  now = time(nullptr);
  //通过一个time()函数得到time_t类型的信息
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  //通过localtime函数将time_t信息转为tm结构体指针类型的信息
  char buff[16];

  display->setTextAlignment(TEXT_ALIGN_CENTER);
  //居中显示
  display->setFont(ArialMT_Plain_16);
  String date = WDAY_NAMES[timeInfo->tm_wday];
 
  // 函数内使用一个宏 PSTR 用来申明Flash中的字符串
  sprintf_P(buff, PSTR("%04d-%02d-%02d  %s"), timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, WDAY_NAMES[timeInfo->tm_wday].c_str());
  //PSTR()就是char*()
  //sprintf_p()发送格式化输出到 buff 所指向的字符串
  display->drawString(64 + x, 5 + y, String(buff));
  //以16的字体输出年份-月份-日期 星期
  display->setFont(ArialMT_Plain_24);

  sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
  display->drawString(64 + x, 22 + y, String(buff));
  //以24字体输出时:分:秒
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}


void drawAnniversary(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y)
{
    int deltaDays,deltaYears;
    returnDelta(deltaDays,deltaYears);

    //输出显示内容
    char buff[16];
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    display->setFont(ArialMT_Plain_10);
    display->drawString(64 + x, 5 + y, "We've been together for");
    if(deltaDays!=0)
    {
      sprintf_P(buff, PSTR("%02d Yrs %03d Dys!"), deltaYears,deltaDays);
      display->setFont(ArialMT_Plain_16);
    }
    else
    {
      sprintf_P(buff, PSTR("Totally %02d YRs!"), deltaYears);
      display->setFont(ArialMT_Plain_16);
    }
    
    
    display->drawString(64 + x, 22 + y, String(buff));


}
void returnDelta(void)
{
    // 获取当地时间信息
    now = time(nullptr);
    long delta_secs = now - memDay;
    int32_t temp_delta_dys = delta_secs/86400;
    delta_dys = String(temp_delta_dys);
}
void returnDelta(int& _deltaDays,int& _deltaYears)
{
    // update the total delta days
    returnDelta();
    struct tm* timeinfo;
    timeinfo = localtime(&now);

    _deltaDays = timeinfo->tm_yday - memDayDt;
    _deltaYears = timeinfo->tm_year - memDayYr;
}


void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //显示天气
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(64 + x, 38 + y, currentWeather.cond_txt + "    |   Wind: " + currentWeather.wind_sc + "  ");
 //以10字体显示天气情况|WInd:风向
  display->setFont(ArialMT_Plain_24);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  String temp = currentWeather.tmp + "°C" ;
  display->drawString(60 + x, 3 + y, temp);
  //以24字体显示温度℃
  display->setFont(ArialMT_Plain_10);
  display->drawString(62 + x, 26 + y, currentWeather.fl + "°C | " + currentWeather.hum + "%");
  //显示体感温度(feellike)℃和湿度%
  display->setFont(Meteocons_Plain_36);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
}

void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //天气预报
  drawForecastDetails(display, x, y, 0);
  drawForecastDetails(display, x + 44, y, 1);
  drawForecastDetails(display, x + 88, y, 2);

}

void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {  //天气预报

  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y, foreWeather[dayIndex].datestr);
  display->setFont(Meteocons_Plain_21);
  display->drawString(x + 20, y + 12, foreWeather[dayIndex].iconMeteoCon);
  //绘制了预报的日期(10号)和天气图标(21号)
  String temp = foreWeather[dayIndex].tmp_min + " | " + foreWeather[dayIndex].tmp_max;
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y + 34, temp);
  //绘制了最低温|最高温
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {   //绘图页眉覆盖
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[14];
  sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);

  display->setColor(WHITE);
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  display->drawString(6, 54, String(buff));
  //描绘出时间
  display->setTextAlignment(TEXT_ALIGN_RIGHT);
  String temp = delta_dys;
  display->drawString(122, 54, temp);
  //描绘出粉丝数
  display->drawHorizontalLine(0, 52, 128);
  //描绘出
}

void setReadyForWeatherUpdate() {  //为天气更新做好准备
  Serial.println("Setting readyForUpdate to true");
  readyForWeatherUpdate = true;
}
